package org.bgee.psp4jdbc.pool;

import java.util.concurrent.atomic.AtomicInteger;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * A <code>PoolElement</code> is developped to be part of a {@link Pool}.
 * 
 * @author Frederic Bastian
 * @version 1, August 2013
 * @see Pool
 * @since 1
 *
 */
public abstract class PoolElement 
{
    /**
     * <code>Logger</code> of the class. 
     */
    private final static Logger log = LogManager.getLogger(PoolElement.class.getName());
    /**
     * A <code>String</code> that is the identifier used to track 
     * this <code>PoolElement</code> in the <code>holderPool</code>.
     */
    private final String id;
    /**
     * The <code>Pool</code> related to this <code>PoolElement</code>. 
     * When this <code>PoolElement</code> is under used, <code>removeFromPool</code> 
     * should be called, so that only one user use it at a time. 
     * When calling <code>removeFromPool</code>, this <code>PoolElement</code> still keeps 
     * the reference to its containing <code>Pool</code>. After use, 
     * <code>ReturnToPool</code> should be called, to be available for another use again.
     * <p>
     * Only when <code>release</code> is called, this <code>PoolElement</code> looses 
     * reference to any <code>Pool</code> to not be able to be used again.
     */
    private volatile Pool holderPool;
    
    /**
     * An <code>int</code> keeping track of the number of times this 
     * <code>PoolElement</code> was used, for LFU evictions.
     * <p>
     * It is declared volatile rather than being an <code>AtomicInteger</code> 
     * because we do not expect several threads to access it a the same time, 
     * rather, sequentially. 
     */
    private final AtomicInteger useCount;
    
    /**
     * An <code>int</code> representing the sequence index of use 
     * of this <code>PoolElement</code>, such as a <code>PoolElement</code> 
     * more recently used will have a greater sequence index than 
     * a <code>PoolElement</code> less recently used. Used for LRU eviction.
     */
    private final AtomicInteger useSequence;
    
    /**
     * Constructor providing the identifier of this <code>PoolElement</code>, and the 
     * <code>Pool</code> holding it. It calls the method <code>returnToThePool</code> 
     * to register this <code>PoolElement</code> to the <code>holderPool</code>.
     * 
     * @param id			a <code>String</code> that is the identifier of this element, 
     * 						used as key in pools.
     * @param holderPool	The <code>Pool</code> making use of 
     * 						this <code>PoolElement</code>. 
     */
    protected PoolElement(String id, Pool holderPool) {
    	this.id = id;
    	this.holderPool = holderPool;
    	this.useCount = new AtomicInteger(0);
    	this.useSequence = new AtomicInteger(0);
    }
    
	/**
	 * @return 	A <code>String</code> that is the identifier used to track 
     * 			this <code>PoolElement</code> in a <code>Pool</code>.  
     * 			if two <code>PoolElement</code> instances have a same ID, 
     * 			it means there are equivalent.
	 */
	protected String getId() {
		return this.id;
	}
	
	/**
	 * Increment the number of times this <code>PoolElement</code> was used, 
	 * for LFU evictions.
	 * <p>
	 * This method is used by the {@link Pool} because it is used as part of 
	 * other atomic operaions.
	 */
	protected void incrementUseCount() {
		this.useCount.incrementAndGet();
	}
	/**
	 * 
	 * @return 	an <code>int</code> that is the number of times this 
     *			<code>PoolElement</code> was used. It is used by the {@link Pool} 
     *			for LFU evictions as part of other atomic operations.
	 */
	protected int getUseCount() {
		return this.useCount.get();
	}
	
	/**
	 * @return 	An <code>int</code> representing the sequence index of use 
     * 			of this <code>PoolElement</code>, such as a <code>PoolElement</code> 
     * 			more recently used will have a greater sequence index than 
     *			a <code>PoolElement</code> less recently used. Used for LRU eviction 
     *          by the {@link Pool} as part of other atomic operations.
	 */
	protected int getUseSequence() {
		return this.useSequence.get();
	}

	/**
	 * @param useSequence 	An <code>int</code> representing the sequence index of use 
     * 						of this <code>PoolElement</code>, such as a <
     * 						code>PoolElement</code> more recently used will have 
     * 						a greater sequence index than a <code>PoolElement</code> 
     * 						less recently used. Used for LRU eviction by the {@link Pool} 
     * 						as part of other atomic operations.
	 */
	protected void setUseSequence(int useSequence) {
		this.useSequence.set(useSequence);
	}

	/**
	 * Make this <code>PoolElement</code> to return to its {@link #holderPool}.
     * @throws ReleaseException	If an error occurred, if trying to release this 
     * 							<code>PoolElement</code>, when another one with the same key 
     * 							is already present in the pool.
	 */
	protected void returnToPool() throws ReleaseException {
		log.entry();
		
		if (this.isReleased()) {
			log.exit(); return;
		}
		PoolElement previousElement = this.holderPool.putIfAbsent(this);
        //if the pool already contained a PoolElement with that key, 
        //it means that several threads access the same pool, and we should release 
        //this PoolElement
        if (previousElement != null) {
            this.release();
        } 
        
        log.exit();
	}
	
	protected void removeFromPool() {
		log.entry();
		if (this.isReleased()) {
			log.exit(); return;
		}
		this.holderPool.remove(this.getId(), this);
		log.exit();
	}
	
	/**
     * Method called whenever this <code>PoolElement</code> is no longer used, 
     * and should not be pooled anymore. 
     * <p>
     * This method can for instance be called when a <code>Pool</code> 
     * drops its least recently used element, because its maximum capacity has been reached. 
     * @throws ReleaseException	If an error occurred while releasing 
     * 							this <code>PoolElement</code>.
     */
    protected void release() throws ReleaseException {
    	log.entry();
    	this.removeFromPool();
    	this.releaseImpl();
    	this.holderPool = null;
    	log.exit();
    }
    /**
     * Return whether this <code>PoolElement</code> has already been released 
     * following a call to <code>release</code>.
     * 
     * @return	<code>true</code> if this <code>PoolElement</code> is already released. 
     */
    public boolean isReleased() {
    	return (this.holderPool == null);
	}
    /**
     * Method called whenever this <code>PoolElement</code> is no longer used, 
     * and should not be pooled anymore. For instance, if this <code>PoolElement</code> 
     * was a <code>PreparedStatement</code>, the real underlying JDBC 
     * <code>PreparedStatement</code> should be closed when this method is called. 
     * The name of this method is not "close" on purpose, to not overlap for instance 
     * with the method that a pooled <code>PreparedStatement</code> would need to provide 
     * to users using it as a plain JDBC class.
     * <p>
     * This method can for instance be called when a <code>Pool</code> 
     * drops its least recently used element, because its maximum capacity has been reached. 
     * @throws Exception	If an error occurred while releasing 
     * 						this <code>PoolElement</code>.
     */
    protected abstract void releaseImpl() throws ReleaseException;
    
    @Override
    public String toString() {
    	return super.toString() + " - ID: " + this.getId() + " - use count: " + 
            this.getUseCount() + " - use sequence: " + this.getUseSequence();
    }
}
